﻿using System;
using System.Collections.Generic;
using System.Linq;
namespace CSUtils {
    public abstract class CDelay0<T> where T: CDelay0<T>, new() {
        protected static string _tag0 = "CDelay0";
        public static int threadId;

        /// <summary>
        /// 延时结束回调
        /// </summary>
        public Action<T> onCallback;
        /// <summary>
        /// 延时被中断回调
        /// </summary>
        public Action<T> onBreakCallback;
        public string keyStr {get;private set;}
        public int key {get;private set;}

        public object callbackObj;

        /// <summary>
        /// 是否停止等待,true停止等待
        /// </summary>
        public bool isStopDelay {get;set;}

        public int currLoopCount {get;internal set;}
        public int loopTotalCount {get;internal set;}

        public System.Diagnostics.StackTrace stackTrace {get;private set;}
        public object noNullGo {get;private set;}
        string noNullGoNullMsg;
        public static bool currIsUseStackTrace {get;set;}
        internal string __strLog;
        internal float __internalTime;
        internal float __lastTime;
        internal int __internalFrame;
        internal int _lastFrame;

        T _Init(string keyStr) {
            this.keyStr = keyStr;
            if(string.IsNullOrEmpty(keyStr)) {
                key = this.GetHashCode();
            } else {
                key = keyStr.GetHashCode();
            }
            __strLog = $"{key}/{keyStr}/{GetHashCode()}";
            if(currIsUseStackTrace) {
                stackTrace = new System.Diagnostics.StackTrace(true);
            }
            currIsUseStackTrace = false;
            return(T)this;
        }

        /// <summary>
        /// 是否停止等待,true停止等待
        /// </summary>
        public void SetBreakStop() {
            if(string.IsNullOrEmpty(noNullGoNullMsg) == false) {
                CLog.E(_tag0, $"等待终止...{key}/{noNullGoNullMsg}");
            }
            onBreakCallback?.Invoke((T)this);
            key = 0;
            onBreakCallback = null;
            isStopDelay = true;
        }

        public T SetCallGo(Object noNullGo, string noNullGoNullMsg = null) {
            if(noNullGo != null) {
                this.noNullGo = noNullGo;
                this.noNullGoNullMsg = noNullGoNullMsg;
                __strLog = $"{key}/{keyStr}/{GetHashCode()}/{noNullGoNullMsg}/{noNullGo}";
            }
            return(T)this;
        }

        public override string ToString()=>__strLog;

        public static T WaitFrame(int delayFrame, bool isLoop = false, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayFrame(delayFrame, cde, isLoop? -1: 1);
            return cde;
        }

        public static T DelayFrame(int delayFrame, bool isLoop = false, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayFrame(delayFrame, cde, isLoop? -1: 1);
            return cde;
        }

        public static T DelayFrame(int delayFrame, int loopCount, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayFrame(delayFrame, cde, loopCount);
            return cde;
        }

        public static T Wait(float duration, string key, bool isLoop = false) {
            var cde = new T()._Init(key);
            _RegisterDelayTime(duration, cde, isLoop? -1: 1);
            return cde;
        }

        public static T Wait(float duration, bool isLoop = false, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayTime(duration, cde, isLoop? -1: 1);
            return cde;
        }

        public static T Delay(float duration, bool isLoop = false, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayTime(duration, cde, isLoop? -1: 1);
            return cde;
        }

        public static T Delay(float duration, int loopCount, string key = null) {
            var cde = new T()._Init(key);
            _RegisterDelayTime(duration, cde, loopCount);
            return cde;
        }

        /// <summary>
        /// 切换线程立刻执行
        /// </summary>
        public static T Invoke(Action<T> onCallback = null) {
            var cde = new T()._Init(null);
            cde.loopTotalCount = 1;
            cde.onCallback = onCallback;
            _jumpThreadList.Add(cde);
            return cde;
        }

        /// <summary>
        /// float渐变
        /// </summary>
        public static T FloatGradual_fixedSpeed(float startValue, float endValue, float speed, Action<float, float> changeEvent = null, Action endCall = null, string key = null) {
            float distance = Math.Abs(endValue - startValue);
            int count = (int)(distance * 60f / (speed * CTime.timeScale)); //一秒60帧 
            return _FloatGradual(startValue, endValue, count, changeEvent, endCall, key);
        }

        /// <summary>
        /// float渐变
        /// </summary>
        public static T FloatGradual_fixedTime(float startValue, float endValue, float time, Action<float, float> changeEvent = null, Action endCall = null, string key = null) {
            int count = (int)(60f * time / CTime.timeScale); //一秒60帧 
            return _FloatGradual(startValue, endValue, count, changeEvent, endCall, key);
        }

        static T _FloatGradual(float startValue, float endValue, int count, Action<float, float> changeEvent = null, Action endCall = null, string key = null) {
            var cde = new T()._Init(key);
            count = count < 1? 1: count;
            float[] posArr = new float[count];
            for(int i = 0; i < count; i++) {
                posArr[i] = CMathf.Lerp(startValue, endValue, (i + 0.5f) / count);
            }
            // CDelayMono.mono.StartCoroutine(_FloatGradual(cde, posArr, endValue, changeEvent, endCall));
            return cde;
        }

        public static void RemoveDelay(string keyStr) {
            if(string.IsNullOrEmpty(keyStr)) {
                //LogE($"RemoveDelay..1.....{keyStr}..........{key_delayObjDic.ToStr(n=>n.GetHashCode())}");
                return;
            }
            var key = keyStr.GetHashCode();
            RemoveDelay(key, keyStr);
        }

        public static void RemoveDelay(int key, string keyStr = null) {
            if(eventHash_updateEventDic.ContainsKey(key)) {
                var cde = eventHash_updateEventDic[key];
                cde.isStopDelay = true;
                //LogE($"RemoveDelay...2..删除事件...{key}/{keyStr}....{cde}");
                eventHash_updateEventDic.Remove(key);
            }
        }

        protected static void _RegisterDelayTime(float internalTime, T cde, int loopTotalCount) {
            cde.__internalTime = CMathf.Clamp(internalTime, 0, float.MaxValue);
            cde.__internalFrame = -2;
            cde.__lastTime = CTime.realtimeSinceStartup;
            cde.loopTotalCount = loopTotalCount;
            eventHash_updateEventDic[cde.key] = cde;
            _isDirty = true;
        }

        protected static void _RegisterDelayFrame(int internalFrame, T cde, int loopTotalCount) {
            cde.__internalTime = -2;
            cde.__internalFrame = CMathf.Clamp(internalFrame, 0, int.MaxValue);
            cde._lastFrame = CTime.frameCount;
            cde.loopTotalCount = loopTotalCount;
            eventHash_updateEventDic[cde.key] = cde;
            _isDirty = true;
        }

        protected static Dictionary<int, T> eventHash_updateEventDic = new Dictionary<int, T>();
        protected static T[] _cdeArr = new T[0];
        protected static bool _isDirty;

        protected static List<T> _jumpThreadList = new List<T>();

        static void _CalcDataArr() {
            if(_isDirty == false) {
                return;
            }
            _isDirty = false;
            _cdeArr = eventHash_updateEventDic.Values.ToArray();
        }

        protected internal static void SetThreadId(int threadId) {
            CDelay0<T>.threadId = threadId;
        }

        protected internal static void IUpdate(string _callLog) {
            if(_isDirty) {
                _CalcDataArr();
            }

            //CLog.i(_tag0, $"{_callLog}...循环执行事件....{CTime.currTimeStr}.....");

            for(int i = 0; i < _cdeArr.Length; i++) {
                var cde = _cdeArr[i];
                if(IsCanInvoke(cde)) {
                    try {
                        _Invoke(cde);
                    } catch(Exception ex) {
                        CLog.Ex(_tag0, $"{_callLog}..错误___", ex);
                    }
                }
            }

            var listCount = _jumpThreadList.Count;
            if(listCount > 0) {
                for(int i = 0; i < listCount; i++) {
                    try {
                        _Invoke(_jumpThreadList[i]);
                        _jumpThreadList[i] = null;
                    } catch(Exception ex) {
                        CLog.Ex(_tag0, "错误___", ex);
                    }
                }
                if(listCount == _jumpThreadList.Count) {
                    _jumpThreadList.Clear();
                }
            }
        }

        static void _Invoke(T cde) {
            if(cde == null) {
                return;
            }
            cde.currLoopCount++;
            try {
                if(cde.isStopDelay) {
                    RemoveDelay(cde.key);
                    cde.onCallback = null;
                    return;
                }
                bool isLoop = cde.loopTotalCount < 0 || cde.currLoopCount < cde.loopTotalCount;
                if(isLoop) {
                    cde.onCallback?.Invoke(cde);
                    //cde.__lastTime = CTime.time + cde.__internalTime;
                } else {
                    RemoveDelay(cde.key);
                    cde.onCallback?.Invoke(cde);
                    cde.onCallback = null;
                }
            } catch(Exception ex) {
                CLog.Ex(_tag0, "程序执行错误", ex);
            }
        }

        static bool IsCanInvoke(T cde) {
            bool isInvoke = false;
            if(cde.__internalFrame > -1) {
                var diffTime = CTime.frameCount - cde._lastFrame;
                if(diffTime > cde.__internalFrame) {
                    isInvoke = true;
                    cde._lastFrame = CTime.frameCount;
                }
            } else if(cde.__internalTime > -1) {
                var diffTime = CTime.realtimeSinceStartup - cde.__lastTime;
                if(diffTime > cde.__internalTime) {
                    isInvoke = true;
                    cde.__lastTime = CTime.realtimeSinceStartup;
                }
            }
            return isInvoke;
        }
    }
}
